package edu.bbu.cshu.wmpc.checking.similarity.levenshtein;

import java.util.ArrayList;

import edu.bbu.cshu.wmpc.checking.similarity.Similarity;
/**
 * <ul>
 * <li><b><i>LevenshteinDistance</i></b></li>
 * <li style="list-style: none;"></li>
 * <li style="list-style: none;">
 * Class that extends {@link Similarity} and implements the {@code levenshtein distance} similarity test.
 * 
 * @author WMPC team
 * </li>
 * </ul>
 */ 
public class LevenshteinDistance extends Similarity {
	/**
	 * <ul>
	 * <li><b><i>line1</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@link ArrayList}<{@link Character}> that contains the first {@code String}. The {@code String} has be processed into characters.
	 * 
	 * </li>
	 * </ul>
	 */
	private ArrayList<Character> line1;
	/**
	 * <ul>
	 * <li><b><i>line1</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * {@link ArrayList}<{@link Character}> that contains the second {@code String}. The {@code String} has be processed into characters.
	 * 
	 * </li>
	 * </ul>
	 */
	private ArrayList<Character> line2;
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>LevenshteinDistance</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code public LevenshteinDistance(double limit)}</p>
	 * <br>	
	 * Constructor that sets the value of {@code limit}.
	 * 
	 * @param limit - the limit at which two lines are considered similar
	 * </li>
	 * </ul>
	 */
	public LevenshteinDistance(double limit) {
		this.limit = limit;
	}

	protected void setString(int index, String str) {
		switch (index){
			case 1: line1 = new ArrayList<Character>(); break;
			case 2: line2 = new ArrayList<Character>(); break;
		}

		char tmp;
		char tmpFront;
		char tmpBack;
		int length = str.length();
		char[] ch = str.toCharArray();

		tmp = ch[0];
		if (specia.indexOf(tmp) != -1) {
			addToLine(index, tmp);
		} else {
			tmpBack = ch[1];
			if (!Character.isWhitespace(tmpBack)) {
				if (!Character.isWhitespace(tmp) && Character.isLetterOrDigit(tmp)) {
					addToLine(index, tmp);
				}
			}
		}

		for (int i = 1; i < (length-1); i++) {
			tmp = ch[i];

			if (specia.indexOf(tmp) != -1) {
				addToLine(index, tmp);
			} else {
				tmpFront = ch[i-1];		
				tmpBack = ch[i+1];

				if (!Character.isWhitespace(tmpFront) || !Character.isWhitespace(tmpBack)) {
					if (!Character.isWhitespace(tmp) && Character.isLetterOrDigit(tmp)) {
						addToLine(index, tmp);
					}
				}
			}
		}

		tmp = ch[length-1];
		tmpFront = ch[length-2];
		if (specia.indexOf(tmp) != -1) {
			addToLine(index, tmp);
		} else {
			if (!Character.isWhitespace(tmpFront)) {

				if (!Character.isWhitespace(tmp) && Character.isLetterOrDigit(tmp)) { 
					addToLine(index, tmp);
				}
			}
		}
	}
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>addToLine</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code private void addToLine(int index, char tmp)}</p>
	 * <br>	
	 * Adds {@code tmp} to {@code line1} or {@code line2} depending on the {@code index}.
	 * 
	 * @param index - specifies to which {@code String} the {@code char} is to be added
	 * @param tmp	- the character to be added
	 * </li>
	 * </ul>
	 */
	private void addToLine(int index, char tmp){
		switch (index) {
			case 1: line1.add(Character.toLowerCase(tmp)); break;
			case 2: line2.add(Character.toLowerCase(tmp)); break;
		}
	}
	/**
	 * <ul style="margin-top: 24px;">
	 * <li><b><i>tripleMin</i></b></li>
	 * <li style="list-style: none;"></li>
	 * <li style="list-style: none;">
	 * <p style="margin-left: 7px;">{@code private int tripleMin(int x, int y, int z)}</p>
	 * <br>	
	 * Determines the smallest of the three given {@code int} values. 
	 * 
	 * @param x - first {@code int} value
	 * @param y - second {@code int} value
	 * @param z - third {@code int} value
	 * @return	the smallest {@code int} value 
	 * </li>
	 * </ul>
	 */
	private int tripleMin(int x, int y, int z) {
		return Math.min(Math.min(x, y), z);
	}

	protected double executeStringTest() {
		int n = line1.size();
		int m = line2.size();

		if (n == 1 || m == 1) {
			return (double) 0;
		}

		int [][] reach = new int[n+1][m+1];

		for (int i = 0; i <= n; i++) {
			reach[i][0] = i;
		}

		for (int j = 0; j <= m; j++) {
			reach[0][j] = j;
		}

		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				reach[i][j] = tripleMin(reach[i-1][j] + 1, reach[i][j-1] + 1, reach[i-1][j-1] + (line1.get(i-1)==line2.get(j-1)?0:1));
			}
		}
		
		double totalSize = n + m;

		return (double) (totalSize - reach[n][m]) / totalSize;
	}

}
